(function (angular) {
    'use strict';
    var formatModule = angular.module('fs.format');
    formatModule.factory('formatService', formatService);
    formatService.$inject = ['$filter', 'storageService', 'FormatterConstants'];
    function formatService($filter, storageService, FormatterConstants) {
        var defaultFormat = FormatterConstants.DEFAULT_FORMATTER;
        function getMaximumIntegerDigits(pattern) {
            if (!pattern)
                return 1;
            if (pattern.indexOf('#') == 0) {
                return 2147483647;
            }
            pattern.replace('[,]', '');
            return pattern.length;
        }
        function getMaximumFractionDigits(pattern) {
            if (!pattern)
                return 0;
            return pattern.length;
        }
        function round(number, decimal) {
            if (isNaN(number)) return '';
            if (decimal < 0)
                return new Big(number).toString();
            return new Big(number).round(decimal).toString();
        }

        function formatNumber(number, pattern) {
            var maximumIntegerDigits, maximumFractionDigits, formatInfo, formatOptions, numberArray, numberString;
            var formatArray, formatString, length, len, i, result = '', sign = '';
            if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
                return;
            }
            formatInfo = storageService.getFormatInfo() || {};
            pattern = formatInfo[pattern] || pattern;
            pattern = pattern || formatInfo.defaultFloat || defaultFormat.number;
            if (!/.*[#0]+.*/.test(pattern.replace(/[^#0,\.]*/, ''))) {
                return number;
            }

            formatOptions = {
                decimalSeparator: formatInfo.decimalSeparator || '.',
                groupSeparator: formatInfo.groupSeparator || ',',
                groupingUsed: pattern.indexOf(',') >= 0,
                groupingSize: ((pattern.indexOf('.') || pattern.length - 1) - pattern.indexOf(',') - 1)
            };

            formatArray = pattern.split('.');
            maximumIntegerDigits = getMaximumIntegerDigits(formatArray[0]);
            maximumFractionDigits = getMaximumFractionDigits(formatArray.length > 1 ? formatArray[1] : '');
            if (isNaN(number)) return '';
            number = round(number, maximumFractionDigits);
            numberString = '' + number;
            numberArray = numberString.split('.');
            if (numberArray[0].length > maximumIntegerDigits) {
                numberArray[0] = numberArray[0].substr(numberArray[0].length - maximumIntegerDigits);
                numberString = numberArray.join('.');
            }

            numberArray = numberString.split(".");
            numberString = numberArray[0];
            formatString = formatArray[0];
            len = numberArray[0].length - 1;
            for (i = formatString.length - 1; i >= 0; i--) {
                switch (formatString.substr(i, 1)) {
                    case '#':
                        if (len >= 0)
                            result = numberString.substr(len--, 1) + result;
                        break;
                    case '0':
                        if (len >= 0)
                            result = numberString.substr(len--, 1) + result;
                        else
                            result = '0' + result;
                        break;
                    case ',':
                        result = formatOptions.groupSeparator + result;
                        break;
                }
            }
            if (len >= 0) {
                if (formatOptions.groupingUsed) {
                    length = numberArray[0].length;
                    for (; len >= 0; len--) {
                        result = numberArray[0].substr(len, 1) + result;
                        if (len > 0 && ((length - len) % formatOptions.groupingSize) == 0)
                            result = formatOptions.groupSeparator + result;
                    }
                } else
                    result = numberString.substr(0, len + 1) + result;
            }
            result += formatOptions.decimalSeparator;
            numberString = numberArray.length > 1 ? numberArray[1] : '';
            formatString = formatArray.length > 1 ? formatArray[1] : '';
            len = 0;
            for (var i = 0; i < formatString.length; i++) {
                switch (formatString.substr(i, 1)) {
                    case '#':
                        if (len < numberString.length)
                            result += numberString.substr(len++, 1);
                        break;
                    case '0':
                        if (len < numberString.length)
                            result += numberString.substr(len++, 1);
                        else
                            result += '0';
                        break;
                }
            }
            if (result.charAt(0) == '-' || result.charAt(0) == '+') {
                sign = result.charAt(0);
                result = result.substring(1);
            }
            result = sign + result.replace(/^\,|^\./, '').replace(/\.$|\,$/, '');
            return result;
        }
        function formatMoney(money, pattern) {// pattern is ccy or ccy format
            // TODU: need to get ccyConfig from backend
            var formatInfo, siteInfo, ccyConfig = FormatterConstants.CCY_CONFIG, zeros = ".000000000000000000000000000000000000000000000000", fractionDigits;
            formatInfo = storageService.getFormatInfo() || {};
            siteInfo = storageService.getSiteInfo() || {};
            if (ccyConfig[angular.uppercase(pattern)] == undefined) {
                fractionDigits = ccyConfig[siteInfo.baseCcy];
            } else {
                fractionDigits = ccyConfig[angular.uppercase(pattern)];
            }
            if (!(money === null || angular.isUndefined(money) || (angular.isString(money) ? money.trim() == '' : false))) {
                pattern = (formatInfo.money0 || '#,##0') + zeros.substr(0, fractionDigits + 1);
            } else {
                pattern = formatInfo[pattern];
                pattern = pattern || formatInfo['defaultMoney'];
            }
            return formatNumber(money, pattern);
        }
        function formatSst(sensitiveInfo, pattern) {
            return $filter("sensitive")(sensitiveInfo, pattern.positiveOffset, pattern.negativeOffset, pattern.mask);
        }
        function formatPercentage(number, pattern) {
            var formatInfo = storageService.getFormatInfo() || {};
            pattern = formatInfo[pattern];
            pattern = pattern || defaultFormat.percentage;
            number = formatNumber(number * 100, pattern);
            return number = number ? number + '%' : number;
        }
        function formatDate(date, pattern) {
            var formatInfo = storageService.getFormatInfo() || {};
            pattern = formatInfo[pattern] || pattern;
            return $filter('dateFormat')(date, pattern);
        }
        function parseNumber(number) {
            var formatInfo, decimalSeparator, groupSeparator;
            if (angular.isNumber(number))
                return number;
            if (!angular.isString(number)) {
                return number;
            }
            if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
                return '';
            }
            formatInfo = storageService.getFormatInfo() || {};
            decimalSeparator = formatInfo.decimalSeparator || '.';
            groupSeparator = formatInfo.groupSeparator || ',';
            number = number.replace(eval("/\\" + groupSeparator + "/g"), "");
            number = number.replace(eval("/\\" + decimalSeparator + "/g"), ".");
            return isNaN(number) ? '' : new Big(number).toString();
        }
        function parseMoney(money) {
            return parseNumber(money);
        }
        function parsePercentage(percentage) {
            if (!percentage || percentage == '') {
                return percentage;
            }
            if (angular.isNumber(percentage))
                return percentage / 100;
            return parseNumber(percentage.replace(/%$/g, '')) / 100;
        }
        function parseDate(date, pattern) {
            var formatInfo, i_val = 0, i_format = 0, c = "", token = "", token2 = "", x, y, year = new Date().getFullYear();
            var month = 1, day = 1, hh = 0, mm = 0, ss = 0, sss = 0, ampm = "", zs = "", zh = 0, zm = 0;
            if (!date || !pattern)
                return date;
            formatInfo = storageService.getFormatInfo() || {};
            pattern = formatInfo[pattern] || pattern;
            // check the value is 0 - 9.
            this.isInteger = function (val) {
                for (var i = 0; i < val.length; i++) {
                    if ("1234567890".indexOf(val.charAt(i)) == -1) {
                        return false;
                    }
                }
                return true;
            };
            // Gets the token from minlength to maxlength.
            this.getInt = function (str, i, minlength, maxlength) {
                for (var x = maxlength; x >= minlength; x--) {
                    var token = str.substring(i, i + x);
                    if (token.length < minlength) {
                        return null;
                    }
                    if (this.isInteger(token)) {
                        return token;
                    }
                }
                return null;
            };

            this.buildDate = function (year, month, day, hh, mm, ss, sss, zh, zm, zs) {
                var date = new Date(0);
                var dateSetter = date.setFullYear;//zs != '' ? date.setUTCFullYear : date.setFullYear;
                var timeSetter = date.setHours;//zs != '' ? date.setUTCHours : date.setHours;
                var h = (hh || 0); //- zh;
                var m = (mm || 0); //- zm;
                var s = (ss || 0);
                var ms = Math.round(parseFloat('0.' + (sss || 0)) * 1000);
                dateSetter.call(date, year, month - 1, day);
                timeSetter.call(date, h, m, s, ms);
                return date;
            };

            while (i_format < pattern.length) {
                c = pattern.charAt(i_format);
                token = "";
                while ((pattern.charAt(i_format) == c) && (i_format < pattern.length)) {
                    token += pattern.charAt(i_format++);
                }
                if (token == "yyyy" || token == "yy" || token == "y") {
                    if (token == "yyyy") {
                        x = 4;
                        y = 4;
                    }
                    if (token == "yy") {
                        x = 2;
                        y = 2;
                    }
                    if (token == "y") {
                        x = 2;
                        y = 4;
                    }
                    year = this.getInt(date, i_val, x, y);
                    if (year == null)
                        return date;
                    i_val += year.length;
                } else if (token == "MM" || token == "M") {
                    month = this.getInt(date, i_val, token.length, 2);
                    if (month == null || month < 1 || month > 12)
                        return date;
                    i_val += month.length;
                } else if (token == "dd" || token == "d") {
                    day = this.getInt(date, i_val, token.length, 2);
                    if (day == null || (day < 1) || (day > 31))
                        return date;
                    i_val += day.length;
                } else if (token == "hh" || token == "h") {
                    hh = this.getInt(date, i_val, token.length, 2);
                    if (hh == null || (hh < 1) || (hh > 12))
                        return date;
                    i_val += hh.length;
                } else if (token == "HH" || token == "H") {
                    hh = this.getInt(date, i_val, token.length, 2);
                    if (hh == null || (hh < 0) || (hh > 23))
                        return date;
                    i_val += hh.length;
                } else if (token == "mm" || token == "m") {
                    mm = this.getInt(date, i_val, token.length, 2);
                    if (mm == null || (mm < 0) || (mm > 59))
                        return null;
                    i_val += mm.length;
                } else if (token == "ss" || token == "s") {
                    ss = this.getInt(date, i_val, token.length, 2);
                    if (ss == null || (ss < 0) || (ss > 59))
                        return date;
                    i_val += ss.length;
                } else if (token == "sss" || token == "SSS" || token == "S") {
                    sss = this.getInt(date, i_val, token.length, 3);
                    if (sss == null || (sss < 0) || (sss > 999))
                        return date;
                    i_val += sss.length;
                } else if (token == "Z" || token == "z") {
                    zs = date.substring(i_val, i_val + 1);
                    zh = this.getInt(date, i_val + 1, 2, 2);
                    zm = this.getInt(date, i_val + 3, 2, 2);
                    zh = zs == '+' ? zh : -zh;
                    zm = zs == '+' ? zm : -zm;
                    i_val += 5;
                } else if (token == "a") {
                    if (date.substring(i_val, i_val + 2).toLowerCase() == "am") {
                        ampm = "AM";
                    } else if (date.substring(i_val, i_val + 2).toLowerCase() == "pm") {
                        ampm = "PM";
                    } else {
                        return date;
                    }
                    i_val += 2;
                } else {
                    if (date.substring(i_val, i_val + token.length) != token) {
                        return date;
                    } else {
                        i_val += token.length;
                    }
                }
            }
            // validate data
            if (i_val != date.length)
                return date;
            // validate month
            if (month == 2) {
                if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
                    if (day > 29)
                        return date;
                } else {
                    if (day > 28)
                        return date;
                }
            }
            if ((month == 4) || (month == 6) || (month == 9) || (month == 11)) {
                if (day > 30)
                    return date;
            }
            // set ampm
            if (hh < 12 && ampm == "PM") {
                hh = hh - 0 + 12;
            } else if (hh > 11 && ampm == "AM") {
                hh -= 12;
            }
            return this.buildDate(year, month, day, hh, mm, ss, sss, zh, zm, zs);
        }
        return {
            formatNumber: formatNumber,
            formatMoney: formatMoney,
            formatPercentage: formatPercentage,
            formatDate: formatDate,
            formatSst: formatSst,
            parseNumber: parseNumber,
            parseMoney: parseMoney,
            parsePercentage: parsePercentage,
            parseDate: parseDate,
            round: round
        };
    }
})(angular);